package com.tensquare.base.service.impl;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.util.IdUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.fastjson.JSON;
import com.tensquare.base.client.GatheringClient;
import com.tensquare.base.domain.Gathering;
import com.tensquare.base.domain.Label;
import com.tensquare.base.mapper.LabelMapper;
import com.tensquare.base.service.ILabelService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import jdk.nashorn.internal.ir.Block;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 标签Service业务层处理
 * @author hbxfwf
 * @date 2021-08-24
 */
@Service
@Slf4j
public class LabelServiceImpl implements ILabelService
{
    @Autowired
    private LabelMapper labelMapper;
    @Autowired
    private GatheringClient gatheringClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 查询标签
     * 
     * @param id 标签主键
     * @return 标签
     */
    @Override
    public Label selectLabelById(String id)
    {
        return labelMapper.selectLabelById(id);
    }

    /**
     * 查询标签列表
     * 
     * @param label 标签
     * @return 标签
     */
    @Override
    public List<Label> selectLabelList(Label label)
    {
        List<Label> labels = null;
        // 1.5.0 版本开始可以利用 try-with-resources 特性（使用有限制）
        // 资源名可使用任意有业务语义的字符串，比如方法名、接口名或其它可唯一标识的字符串。
        try (Entry entry = SphU.entry("selectLabelList")) {
            // 被保护的业务逻辑
             labels = labelMapper.selectLabelList(label);
        } catch (BlockException ex) {
            // 资源访问阻止，被限流或被降级
            // 在此处进行相应的处理操作
            log.error("抛出异常的方式定义资源：{}",ex);
        }
        return labels;
    }

    /**
     * 新增标签
     * 
     * @param label 标签
     * @return 结果
     */
    @Override
    public int insertLabel(Label label)
    {
        return labelMapper.insertLabel(label);
    }

    /**
     * 修改标签
     * 
     * @param label 标签
     * @return 结果
     */
    @Override
    public int updateLabel(Label label)
    {
        return labelMapper.updateLabel(label);
    }

    /**
     * 批量删除标签
     * 
     * @param ids 需要删除的标签主键
     * @return 结果
     */
    @Override
    public int deleteLabelByIds(String[] ids)
    {
        return labelMapper.deleteLabelByIds(ids);
    }

    /**
     * 删除标签信息
     * 
     * @param id 标签主键
     * @return 结果
     */
    @Override
    public int deleteLabelById(String id)
    {
        return labelMapper.deleteLabelById(id);
    }

    /**
     * 功能: 分布式事务
     * 参数:
     * 返回值: void
     * 时间: 2021/9/10 13:54
     */
    @Override
    @GlobalTransactional
    @Transactional
    public void save(Label label) {

            log.info("当前 XID: {}", RootContext.getXID());
            labelMapper.insertLabel(label);
            Gathering gathering = new Gathering();
            gathering.setId(IdUtil.createSnowflake(0,1).nextIdStr());
            gathering.setAddress("上海");
            gathering.setCity("上海");
            gathering.setName("举办演唱会");
            gathering.setState("1");
            gathering.setCreateTime(new Date());
            gathering.setUpdateTime(new Date());
            gathering.setStarttime(new Date());

            gatheringClient.add2(gathering);


    }

    /**
     * 功能: 对自定义资源进行熔断限流降级保护 ,注解@SentinelResource的一些属性说明：
     * 详见：https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
     * ① value：资源名称
     * ② fallback：当出现了熔断或降级时由它指定的函数进行处理，此函数与注解@SentinelResource一样，
     * ③ blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称，
     * 可选项。blockHandler 函数访问范围需要是 public，返回类型需要与原方法相匹配，参数类型需要和原方法
     * 相匹配并且最后加一个额外的参数，类型为 BlockException。blockHandler 函数默认需要和原方法在同一
     * 个类中。若希望使用其他类的函数，则可以指定 blockHandlerClass 为对应的类的 Class 对象，注意对应的
     * 函数必需为 static 函数，否则无法解析。
     */
    int i = 1;
    @Override
    @SentinelResource(value="selectLabelList2",/*fallback = "labelFallBack",*/blockHandler = "blockHandler",blockHandlerClass = MyBlockHandler.class)
    public List<Label> selectLabelList2(Label label) throws BlockException{
        if(i == 1){
            throw new FlowException("调用出现了问题。。。");
        }
        return labelMapper.selectLabelList(label);
    }



    public List<Label> labelBlockHandler(Label label,BlockException e) {
        log.error("开始熔断[labelBlockHandler]：{}",e);
        return null;
    }
    public List<Label> labelFallBack(Label label,Throwable e) {
        log.error("开始熔断[labelFallBack]：{}",e.getMessage());
        return null;
    }

    /**
     * 功能: 分布式锁（redis + Lua脚本）
     * 注意：
     *   此种方式，因为不能对锁自动续期，所以，会出现，业务还未完成，锁就己经到期，导致后面的微服务不能加锁！
     */
    @Override
    public List<Label> selectLabelList3(Label label) {
        List<Label> labels = null;
        //1. 得到一个uuid的大字符串
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        //2. 判断是否得到lock（是否true）
        if(lock){
            try {
                //2.1 从redis中得到所有的标签列表
                String labelList = redisTemplate.opsForValue().get("labelList");
                //2.2 判断是否存在，如果存在，就直接返回 ，否则，从数据库中取并添加到redis中
                if(StringUtils.isNotBlank(labelList)){
                    labels = JSON.parseArray(labelList, Label.class);
                    System.out.println("缓存命中。。。");
                }else{
                    System.out.println("正在从数据库中获取数据。。。");
                    //2.3 如果不存在，就从数据库中加载数据
                    labels = labelMapper.selectLabelList(null);
                    //2.4  将其放到redis中
                    redisTemplate.opsForValue().set("labelList",JSON.toJSONString(labels));
                }
            }  finally {
                //2.5 删除哪把锁
                //2.5.1 定义执行的redis脚本
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                //2.5.2 构造脚本对象(泛型代表脚本执行后的返回值类型)
                RedisScript<Long>  redisScript = new DefaultRedisScript(script,Long.class);
                redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
            }
            return labels;
        }else{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return selectLabelList3(label);
        }

    }

    /**
     * 功能: 使用redisson实现分布式锁
     * 底层使用的看门狗机制：实现了自动续期，每隔10s自动续期！
     */
    @Override
    public List<Label> selectLabelList4(Label label) {
        //1. 获取锁对象
        RLock lock = redissonClient.getLock("lock");
        //2. 加锁
        lock.lock(20,TimeUnit.SECONDS);
        List<Label> labels = null;
        try{
            //2.1 从redis中得到所有的标签列表
            String labelList = redisTemplate.opsForValue().get("labelList");
            //2.2 判断是否存在，如果存在，就直接返回 ，否则，从数据库中取并添加到redis中
            if(StringUtils.isNotBlank(labelList)){
                labels = JSON.parseArray(labelList, Label.class);
                System.out.println("缓存命中。。。");
            }else{
                System.out.println("正在从数据库中获取数据。。。");
                //2.3 如果不存在，就从数据库中加载数据
                labels = labelMapper.selectLabelList(null);
                //2.4  将其放到redis中
                redisTemplate.opsForValue().set("labelList",JSON.toJSONString(labels));
            }
        }finally {
            //释放锁
            lock.unlock();
        }

        return labels;
    }
}
